home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February (DVD) / PCWorld_2008-02_DVD.iso / v cisle / PHP / PHP.exe / xampp-win32-1.6.5-installer.exe / php / PEAR / phing / Project.php < prev    next >
Encoding:
PHP Script  |  2007-12-20  |  32.5 KB  |  967 lines

  1. <?php
  2. /*
  3.  *  $Id: Project.php,v 1.29 2006/02/02 20:27:10 hlellelid Exp $
  4.  *
  5.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16.  *
  17.  * This software consists of voluntary contributions made by many individuals
  18.  * and is licensed under the LGPL. For more information please see
  19.  * <http://phing.info>.
  20.  */
  21.  
  22. define('PROJECT_MSG_DEBUG', 4);
  23. define('PROJECT_MSG_VERBOSE', 3);
  24. define('PROJECT_MSG_INFO', 2);
  25. define('PROJECT_MSG_WARN', 1);
  26. define('PROJECT_MSG_ERR', 0);
  27.  
  28. include_once 'phing/system/io/PhingFile.php';
  29. include_once 'phing/util/FileUtils.php';
  30. include_once 'phing/TaskAdapter.php';
  31. include_once 'phing/util/StringHelper.php';
  32. include_once 'phing/BuildEvent.php';
  33. include_once 'phing/input/DefaultInputHandler.php';
  34.  
  35. /**
  36.  *  The Phing project class. Represents a completely configured Phing project.
  37.  *  The class defines the project and all tasks/targets. It also contains
  38.  *  methods to start a build as well as some properties and FileSystem
  39.  *  abstraction.
  40.  *
  41.  * @author    Andreas Aderhold <andi@binarycloud.com>
  42.  * @author    Hans Lellelid <hans@xmpl.org>
  43.  * @version   $Revision: 1.29 $
  44.  * @package   phing
  45.  */
  46. class Project {
  47.  
  48.     /** contains the targets */
  49.     private $targets         = array();
  50.     /** global filterset (future use) */
  51.     private $globalFilterSet = array();
  52.     /**  all globals filters (future use) */
  53.     private $globalFilters   = array();
  54.     
  55.     /** Project properties map (usually String to String). */
  56.     private $properties = array();
  57.     
  58.     /**
  59.      * Map of "user" properties (as created in the Ant task, for example).
  60.      * Note that these key/value pairs are also always put into the
  61.      * project properties, so only the project properties need to be queried.
  62.      * Mapping is String to String.
  63.      */
  64.     private $userProperties = array();
  65.     
  66.     /**
  67.      * Map of inherited "user" properties - that are those "user"
  68.      * properties that have been created by tasks and not been set
  69.      * from the command line or a GUI tool.
  70.      * Mapping is String to String.
  71.      */
  72.     private $inheritedProperties = array();
  73.     
  74.     /** task definitions for this project*/
  75.     private $taskdefs = array();
  76.     
  77.     /** type definitions for this project */
  78.     private $typedefs = array();
  79.     
  80.     /** holds ref names and a reference to the referred object*/
  81.     private $references = array();
  82.     
  83.     /** The InputHandler being used by this project. */
  84.     private $inputHandler;
  85.     
  86.     /* -- properties that come in via xml attributes -- */
  87.     
  88.     /** basedir (PhingFile object) */
  89.     private $basedir;
  90.     
  91.     /** the default target name */
  92.     private $defaultTarget = 'all';
  93.     
  94.     /** project name (required) */
  95.     private $name;
  96.     
  97.     /** project description */
  98.     private $description;
  99.  
  100.     /** a FileUtils object */
  101.     private $fileUtils;
  102.     
  103.     /**  Build listeneers */
  104.     private $listeners = array();
  105.  
  106.     /**
  107.      *  Constructor, sets any default vars.
  108.      */
  109.     function __construct() {
  110.         $this->fileUtils = new FileUtils();
  111.         $this->inputHandler = new DefaultInputHandler();
  112.     }
  113.  
  114.     /**
  115.      * Sets the input handler
  116.      */
  117.     public function setInputHandler(InputHandler $handler) {
  118.         $this->inputHandler = $handler;
  119.     }
  120.  
  121.     /**
  122.      * Retrieves the current input handler.
  123.      */
  124.     public function getInputHandler() {
  125.         return $this->inputHandler;
  126.     }
  127.  
  128.     /** inits the project, called from main app */
  129.     function init() {
  130.         // set builtin properties
  131.         $this->setSystemProperties();
  132.         
  133.         // load default tasks
  134.         $taskdefs = Phing::getResourcePath("phing/tasks/defaults.properties");
  135.         
  136.         try { // try to load taskdefs
  137.             $props = new Properties();
  138.             $in = new PhingFile((string)$taskdefs);
  139.  
  140.             if ($in === null) {
  141.                 throw new BuildException("Can't load default task list");
  142.             }
  143.             $props->load($in);
  144.  
  145.             $enum = $props->propertyNames();
  146.             foreach($enum as $key) {
  147.                 $value = $props->getProperty($key);
  148.                 $this->addTaskDefinition($key, $value);
  149.             }
  150.         } catch (IOException $ioe) {
  151.             throw new BuildException("Can't load default task list");
  152.         }
  153.  
  154.         // load default tasks
  155.         $typedefs = Phing::getResourcePath("phing/types/defaults.properties");
  156.  
  157.         try { // try to load typedefs
  158.             $props = new Properties();
  159.             $in    = new PhingFile((string)$typedefs);
  160.             if ($in === null) {
  161.                 throw new BuildException("Can't load default datatype list");
  162.             }
  163.             $props->load($in);
  164.  
  165.             $enum = $props->propertyNames();
  166.             foreach($enum as $key) {
  167.                 $value = $props->getProperty($key);
  168.                 $this->addDataTypeDefinition($key, $value);
  169.             }
  170.         } catch(IOException $ioe) {
  171.             throw new BuildException("Can't load default datatype list");
  172.         }
  173.     }
  174.  
  175.     /** returns the global filterset (future use) */
  176.     function getGlobalFilterSet() {
  177.         return $this->globalFilterSet;
  178.     }
  179.  
  180.     // ---------------------------------------------------------
  181.     // Property methods
  182.     // ---------------------------------------------------------
  183.     
  184.     /**
  185.      * Sets a property. Any existing property of the same name
  186.      * is overwritten, unless it is a user property.
  187.      * @param string $name The name of property to set.
  188.      *             Must not be <code>null</code>.
  189.      * @param string $value The new value of the property.
  190.      *              Must not be <code>null</code>.
  191.      * @return void
  192.      */
  193.     public function setProperty($name, $value) {
  194.     
  195.         // command line properties take precedence
  196.         if (isset($this->userProperties[$name])) {
  197.             $this->log("Override ignored for user property " . $name, PROJECT_MSG_VERBOSE);
  198.             return;
  199.         }
  200.  
  201.         if (isset($this->properties[$name])) {
  202.             $this->log("Overriding previous definition of property " . $name, PROJECT_MSG_VERBOSE);
  203.         }
  204.  
  205.         $this->log("Setting project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
  206.         $this->properties[$name] = $value;
  207.     }
  208.  
  209.     /**
  210.      * Sets a property if no value currently exists. If the property
  211.      * exists already, a message is logged and the method returns with
  212.      * no other effect.
  213.      *
  214.      * @param string $name The name of property to set.
  215.      *             Must not be <code>null</code>.
  216.      * @param string $value The new value of the property.
  217.      *              Must not be <code>null</code>.
  218.      * @since 2.0
  219.      */
  220.     public function setNewProperty($name, $value) {
  221.         if (isset($this->properties[$name])) {
  222.             $this->log("Override ignored for property " . $name, PROJECT_MSG_DEBUG);
  223.             return;
  224.         }
  225.         $this->log("Setting project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
  226.         $this->properties[$name] = $value;
  227.     }
  228.  
  229.     /**
  230.      * Sets a user property, which cannot be overwritten by
  231.      * set/unset property calls. Any previous value is overwritten.
  232.      * @param string $name The name of property to set.
  233.      *             Must not be <code>null</code>.
  234.      * @param string $value The new value of the property.
  235.      *              Must not be <code>null</code>.
  236.      * @see #setProperty()
  237.      */
  238.     public function setUserProperty($name, $value) {
  239.         $this->log("Setting ro project property: " . $name . " -> " . $value, PROJECT_MSG_DEBUG);
  240.         $this->userProperties[$name] = $value;
  241.         $this->properties[$name] = $value;
  242.     }
  243.  
  244.     /**
  245.      * Sets a user property, which cannot be overwritten by set/unset
  246.      * property calls. Any previous value is overwritten. Also marks
  247.      * these properties as properties that have not come from the
  248.      * command line.
  249.      *
  250.      * @param string $name The name of property to set.
  251.      *             Must not be <code>null</code>.
  252.      * @param string $value The new value of the property.
  253.      *              Must not be <code>null</code>.
  254.      * @see #setProperty()
  255.      */
  256.     public function setInheritedProperty($name, $value) {
  257.         $this->inheritedProperties[$name] = $value;
  258.         $this->setUserProperty($name, $value);
  259.     }
  260.  
  261.     /**
  262.      * Sets a property unless it is already defined as a user property
  263.      * (in which case the method returns silently).
  264.      *
  265.      * @param name The name of the property.
  266.      *             Must not be <code>null</code>.
  267.      * @param value The property value. Must not be <code>null</code>.
  268.      */
  269.     private function setPropertyInternal($name, $value) {
  270.         if (isset($this->userProperties[$name])) {
  271.             $this->log("Override ignored for user property " . $name, PROJECT_MSG_VERBOSE);
  272.             return;
  273.         }
  274.         $this->properties[$name] = $value;
  275.     }
  276.  
  277.     /**
  278.      * Returns the value of a property, if it is set.
  279.      *
  280.      * @param string $name The name of the property.
  281.      *             May be <code>null</code>, in which case
  282.      *             the return value is also <code>null</code>.
  283.      * @return string The property value, or <code>null</code> for no match
  284.      *         or if a <code>null</code> name is provided.
  285.      */
  286.     public function getProperty($name) {
  287.         if (!isset($this->properties[$name])) {
  288.             return null;
  289.         }
  290.         return $this->properties[$name];
  291.     }
  292.  
  293.     /**
  294.      * Replaces ${} style constructions in the given value with the
  295.      * string value of the corresponding data types.
  296.      *
  297.      * @param value The string to be scanned for property references.
  298.      *              May be <code>null</code>.
  299.      *
  300.      * @return the given string with embedded property names replaced
  301.      *         by values, or <code>null</code> if the given string is
  302.      *         <code>null</code>.
  303.      *
  304.      * @exception BuildException if the given value has an unclosed
  305.      *                           property name, e.g. <code>${xxx</code>
  306.      */
  307.     public function replaceProperties($value) {
  308.         return ProjectConfigurator::replaceProperties($this, $value, $this->properties);
  309.     }
  310.  
  311.     /**
  312.      * Returns the value of a user property, if it is set.
  313.      *
  314.      * @param string $name The name of the property.
  315.      *             May be <code>null</code>, in which case
  316.      *             the return value is also <code>null</code>.
  317.      * @return string  The property value, or <code>null</code> for no match
  318.      *         or if a <code>null</code> name is provided.
  319.      */
  320.      public function getUserProperty($name) {
  321.         if (!isset($this->userProperties[$name])) {
  322.             return null;
  323.         }
  324.         return $this->userProperties[$name];
  325.     }
  326.  
  327.     /**
  328.      * Returns a copy of the properties table.
  329.      * @return array A hashtable containing all properties
  330.      *         (including user properties).
  331.      */
  332.     public function getProperties() {
  333.         return $this->properties;
  334.     }
  335.  
  336.     /**
  337.      * Returns a copy of the user property hashtable
  338.      * @return a hashtable containing just the user properties
  339.      */
  340.     public function getUserProperties() {
  341.         return $this->userProperties;
  342.     }
  343.  
  344.     /**
  345.      * Copies all user properties that have been set on the command
  346.      * line or a GUI tool from this instance to the Project instance
  347.      * given as the argument.
  348.      *
  349.      * <p>To copy all "user" properties, you will also have to call
  350.      * {@link #copyInheritedProperties copyInheritedProperties}.</p>
  351.      *
  352.      * @param Project $other the project to copy the properties to.  Must not be null.
  353.      * @return void
  354.      * @since phing 2.0
  355.      */
  356.     public function copyUserProperties(Project $other) {        
  357.         foreach($this->userProperties as $arg => $value) {
  358.             if (isset($this->inheritedProperties[$arg])) {
  359.                 continue;
  360.             }
  361.             $other->setUserProperty($arg, $value);
  362.         }
  363.     }
  364.  
  365.     /**
  366.      * Copies all user properties that have not been set on the
  367.      * command line or a GUI tool from this instance to the Project
  368.      * instance given as the argument.
  369.      *
  370.      * <p>To copy all "user" properties, you will also have to call
  371.      * {@link #copyUserProperties copyUserProperties}.</p>
  372.      *
  373.      * @param other the project to copy the properties to.  Must not be null.
  374.      *
  375.      * @since phing 2.0
  376.      */
  377.     public function copyInheritedProperties(Project $other) {
  378.         foreach($this->userProperties as $arg => $value) {
  379.             if ($other->getUserProperty($arg) !== null) {
  380.                 continue;
  381.             }
  382.             $other->setInheritedProperty($arg, $value);
  383.         }        
  384.     }
  385.     
  386.     // ---------------------------------------------------------
  387.     //  END Properties methods
  388.     // ---------------------------------------------------------
  389.  
  390.  
  391.     function setDefaultTarget($targetName) {
  392.         $this->defaultTarget = (string) trim($targetName);
  393.     }
  394.  
  395.     function getDefaultTarget() {
  396.         return (string) $this->defaultTarget;
  397.     }
  398.  
  399.     /**
  400.      * Sets the name of the current project
  401.      *
  402.      * @param    string   name of project
  403.      * @return   void
  404.      * @access   public
  405.      * @author   Andreas Aderhold, andi@binarycloud.com
  406.      */
  407.  
  408.     function setName($name) {
  409.         $this->name = (string) trim($name);
  410.         $this->setProperty("phing.project.name", $this->name);
  411.     }
  412.  
  413.     /**
  414.      * Returns the name of this project
  415.      *
  416.      * @returns  string  projectname
  417.      * @access   public
  418.      * @author   Andreas Aderhold, andi@binarycloud.com
  419.      */
  420.     function getName() {
  421.         return (string) $this->name;
  422.     }
  423.  
  424.     /** Set the projects description */
  425.     function setDescription($description) {
  426.         $this->description = (string) trim($description);
  427.     }
  428.  
  429.     /** return the description, null otherwise */
  430.     function getDescription() {
  431.         return $this->description;
  432.     }
  433.  
  434.     /** Set basedir object from xml*/
  435.     function setBasedir($dir) {
  436.         if ($dir instanceof PhingFile) {
  437.             $dir = $dir->getAbsolutePath();
  438.         }
  439.  
  440.         $dir = $this->fileUtils->normalize($dir);
  441.  
  442.         $dir = new PhingFile((string) $dir);
  443.         if (!$dir->exists()) {
  444.             throw new BuildException("Basedir ".$dir->getAbsolutePath()." does not exist");
  445.         }
  446.         if (!$dir->isDirectory()) {
  447.             throw new BuildException("Basedir ".$dir->getAbsolutePath()." is not a directory");
  448.         }
  449.         $this->basedir = $dir;
  450.         $this->setPropertyInternal("project.basedir", $this->basedir->getAbsolutePath());
  451.         $this->log("Project base dir set to: " . $this->basedir->getPath(), PROJECT_MSG_VERBOSE);
  452.         
  453.         // [HL] added this so that ./ files resolve correctly.  This may be a mistake ... or may be in wrong place.                
  454.         chdir($dir->getAbsolutePath());
  455.     }
  456.  
  457.     /**
  458.      * Returns the basedir of this project
  459.      *
  460.      * @returns  PhingFile  Basedir PhingFile object
  461.      * @access   public
  462.      * @throws   BuildException
  463.      * @author   Andreas Aderhold, andi@binarycloud.com
  464.      */
  465.     function getBasedir() {
  466.         if ($this->basedir === null) {            
  467.             try { // try to set it
  468.                 $this->setBasedir(".");
  469.             } catch (BuildException $exc) {
  470.                 throw new BuildException("Can not set default basedir. ".$exc->getMessage());
  471.             }
  472.         }
  473.         return $this->basedir;
  474.     }
  475.  
  476.     /**
  477.      * Sets system properties and the environment variables for this project.
  478.      * 
  479.      * @return void
  480.      */
  481.     function setSystemProperties() {
  482.         
  483.         // first get system properties
  484.         $systemP = array_merge( self::getProperties(), Phing::getProperties() );
  485.         foreach($systemP as $name => $value) {
  486.             $this->setPropertyInternal($name, $value);
  487.         }
  488.         
  489.         // and now the env vars
  490.         foreach($_SERVER as $name => $value) {
  491.             // skip arrays
  492.             if (is_array($value)) {
  493.                 continue;
  494.             }
  495.             $this->setPropertyInternal('env.' . $name, $value);
  496.         }
  497.         return true;
  498.     }
  499.  
  500.  
  501.     /**
  502.      * Adds a task definition.
  503.      * @param string $name Name of tag.
  504.      * @param string $class The class path to use.
  505.      * @param string $classpath The classpat to use.
  506.      */
  507.     function addTaskDefinition($name, $class, $classpath = null) {
  508.         $name  = $name;
  509.         $class = $class;
  510.         if ($class === "") {
  511.             $this->log("Task $name has no class defined.", PROJECT_MSG_ERR);
  512.         }  elseif (!isset($this->taskdefs[$name])) {
  513.             Phing::import($class, $classpath);
  514.             $this->taskdefs[$name] = $class;
  515.             $this->log("  +Task definiton: $name ($class)", PROJECT_MSG_DEBUG);
  516.         } else {
  517.             $this->log("Task $name ($class) already registerd, skipping", PROJECT_MSG_VERBOSE);
  518.         }
  519.     }
  520.  
  521.     function &getTaskDefinitions() {
  522.         return $this->taskdefs;
  523.     }
  524.  
  525.     /**
  526.      * Adds a data type definition.
  527.      * @param string $name Name of tag.
  528.      * @param string $class The class path to use.
  529.      * @param string $classpath The classpat to use.
  530.      */
  531.     function addDataTypeDefinition($typeName, $typeClass, $classpath = null) {    
  532.         if (!isset($this->typedefs[$typeName])) {        
  533.             Phing::import($typeClass, $classpath);
  534.             $this->typedefs[$typeName] = $typeClass;
  535.             $this->log("  +User datatype: $typeName ($typeClass)", PROJECT_MSG_DEBUG);
  536.         } else {
  537.             $this->log("Type $name ($class) already registerd, skipping", PROJECT_MSG_VERBOSE);
  538.         }
  539.     }
  540.  
  541.     function getDataTypeDefinitions() {
  542.         return $this->typedefs;
  543.     }
  544.  
  545.     /** add a new target to the project */
  546.     function addTarget($targetName, &$target) {
  547.         if (isset($this->targets[$targetName])) {
  548.             throw new BuildException("Duplicate target: $targetName");
  549.         }
  550.         $this->addOrReplaceTarget($targetName, $target);
  551.     }
  552.  
  553.     function addOrReplaceTarget($targetName, &$target) {
  554.         $this->log("  +Target: $targetName", PROJECT_MSG_DEBUG);
  555.         $target->setProject($this);
  556.         $this->targets[$targetName] = $target;
  557.     }
  558.  
  559.     function getTargets() {
  560.         return $this->targets;
  561.     }
  562.  
  563.     /**
  564.      * Create a new task instance and return reference to it. This method is
  565.      * sorta factory like. A _local_ instance is created and a reference returned to
  566.      * that instance. Usually PHP destroys local variables when the function call
  567.      * ends. But not if you return a reference to that variable.
  568.      * This is kinda error prone, because if no reference exists to the variable
  569.      * it is destroyed just like leaving the local scope with primitive vars. There's no
  570.      * central place where the instance is stored as in other OOP like languages.
  571.      *
  572.      * [HL] Well, ZE2 is here now, and this is  still working. We'll leave this alone
  573.      * unless there's any good reason not to.
  574.      *
  575.      * @param    string    $taskType    Task name
  576.      * @returns  Task                A task object
  577.      * @throws   BuildException
  578.      *           Exception
  579.      */
  580.     function createTask($taskType) {
  581.         try {
  582.             $cls = "";
  583.             $tasklwr = strtolower($taskType);
  584.             foreach ($this->taskdefs as $name => $class) {
  585.                 if (strtolower($name) === $tasklwr) {
  586.                     $cls = StringHelper::unqualify($class);                                    
  587.                     break;
  588.                 }
  589.             }
  590.             
  591.             if ($cls === "") {
  592.                 return null;
  593.             }
  594.             
  595.             if (!class_exists($cls)) {
  596.                 throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)");
  597.             }
  598.             
  599.             $o = new $cls();        
  600.     
  601.             if ($o instanceof Task) {
  602.                 $task = $o;
  603.             } else {
  604.                 $this->log ("  (Using TaskAdapter for: $taskType)", PROJECT_MSG_DEBUG);
  605.                 // not a real task, try adapter
  606.                 $taskA = new TaskAdapter();
  607.                 $taskA->setProxy($o);
  608.                 $task = $taskA;
  609.             }
  610.             $task->setProject($this);
  611.             $task->setTaskType($taskType);
  612.             // set default value, can be changed by the user
  613.             $task->setTaskName($taskType);
  614.             $this->log ("  +Task: " . $taskType, PROJECT_MSG_DEBUG);
  615.         } catch (Exception $t) {
  616.             throw new BuildException("Could not create task of type: " . $taskType, $t);
  617.         }
  618.         // everything fine return reference
  619.         return $task;
  620.     }
  621.  
  622.     /**
  623.      * Create a task instance and return reference to it
  624.      * See createTask() for explanation how this works
  625.      *
  626.      * @param    string   Type name
  627.      * @returns  object   A datatype object
  628.      * @throws   BuildException
  629.      *           Exception
  630.      */
  631.     function createDataType($typeName) {        
  632.         try {
  633.             $cls = "";
  634.             $typelwr = strtolower($typeName);
  635.             foreach ($this->typedefs as $name => $class) {
  636.                 if (strtolower($name) === $typelwr) {
  637.                     $cls = StringHelper::unqualify($class);                                    
  638.                     break;
  639.                 }
  640.             }
  641.             
  642.             if ($cls === "") {
  643.                 return null;
  644.             }
  645.             
  646.             if (!class_exists($cls)) {
  647.                 throw new BuildException("Could not instantiate class $cls, even though a class was specified. (Make sure that the specified class file contains a class with the correct name.)");
  648.             }
  649.             
  650.             $type = new $cls();
  651.             $this->log("  +Type: $typeName", PROJECT_MSG_DEBUG);
  652.             if (!($type instanceof DataType)) {
  653.                 throw new Exception("$class is not an instance of phing.types.DataType");
  654.             }
  655.             if ($type instanceof ProjectComponent) {
  656.                 $type->setProject($this);
  657.             }
  658.         } catch (Exception $t) {
  659.             throw new BuildException("Could not create type: $typeName", $t);
  660.         }
  661.         // everything fine return reference
  662.         return $type;
  663.     }
  664.  
  665.     /**
  666.      * Executes a list of targets
  667.      *
  668.      * @param    array  List of target names to execute
  669.      * @returns  void
  670.      * @throws   BuildException
  671.      */
  672.     function executeTargets($targetNames) {
  673.         foreach($targetNames as $tname) {
  674.             $this->executeTarget($tname);
  675.         }
  676.     }
  677.  
  678.     /**
  679.      * Executes a target
  680.      *
  681.      * @param    string  Name of Target to execute
  682.      * @returns  void
  683.      * @throws   BuildException
  684.      */
  685.     function executeTarget($targetName) {
  686.  
  687.         // complain about executing void
  688.         if ($targetName === null) {
  689.             throw new BuildException("No target specified");
  690.         }
  691.  
  692.         // invoke topological sort of the target tree and run all targets
  693.         // until targetName occurs.
  694.         $sortedTargets = $this->_topoSort($targetName, $this->targets);        
  695.  
  696.         $curIndex = (int) 0;
  697.         $curTarget = null;
  698.         do {
  699.             try {
  700.                 $curTarget = $sortedTargets[$curIndex++];
  701.                 $curTarget->performTasks();
  702.             } catch (BuildException $exc) {
  703.                 $this->log("Execution of target \"".$curTarget->getName()."\" failed for the following reason: ".$exc->getMessage(), PROJECT_MSG_ERR);
  704.                 throw $exc;
  705.             }
  706.         } while ($curTarget->getName() !== $targetName);
  707.     }
  708.  
  709.  
  710.     function resolveFile($fileName, $rootDir = null) {
  711.         if ($rootDir === null) {
  712.             return $this->fileUtils->resolveFile($this->basedir, $fileName);
  713.         } else {
  714.             return $this->fileUtils->resolveFile($rootDir, $fileName);
  715.         }
  716.     }    
  717.  
  718.     /**
  719.      * Topologically sort a set of Targets.
  720.      * @param  $root is the (String) name of the root Target. The sort is
  721.      *         created in such a way that the sequence of Targets until the root
  722.      *         target is the minimum possible such sequence.
  723.      * @param  $targets is a array representing a "name to Target" mapping
  724.      * @return An array of Strings with the names of the targets in
  725.      *         sorted order.
  726.      */
  727.     function _topoSort($root, &$targets) {
  728.  
  729.         $root     = (string) $root;
  730.         $ret      = array();
  731.         $state    = array();
  732.         $visiting = array();
  733.  
  734.         // We first run a DFS based sort using the root as the starting node.
  735.         // This creates the minimum sequence of Targets to the root node.
  736.         // We then do a sort on any remaining unVISITED targets.
  737.         // This is unnecessary for doing our build, but it catches
  738.         // circular dependencies or missing Targets on the entire
  739.         // dependency tree, not just on the Targets that depend on the
  740.         // build Target.
  741.  
  742.         $this->_tsort($root, $targets, $state, $visiting, $ret);
  743.  
  744.         $retHuman = "";
  745.         for ($i=0, $_i=count($ret); $i < $_i; $i++) {
  746.             $retHuman .= $ret[$i]->toString()." ";
  747.         }
  748.         $this->log("Build sequence for target '$root' is: $retHuman", PROJECT_MSG_VERBOSE);
  749.  
  750.         $keys = array_keys($targets);
  751.         while($keys) {
  752.             $curTargetName = (string) array_shift($keys);
  753.             if (!isset($state[$curTargetName])) {
  754.                 $st = null;
  755.             } else {
  756.                 $st = (string) $state[$curTargetName];
  757.             }
  758.  
  759.             if ($st === null) {
  760.                 $this->_tsort($curTargetName, $targets, $state, $visiting, $ret);
  761.             } elseif ($st === "VISITING") {
  762.                 throw new Exception("Unexpected node in visiting state: $curTargetName");
  763.             }
  764.         }
  765.  
  766.         $retHuman = "";
  767.         for ($i=0,$_i=count($ret); $i < $_i; $i++) {
  768.             $retHuman .= $ret[$i]->toString()." ";
  769.         }
  770.         $this->log("Complete build sequence is: $retHuman", PROJECT_MSG_VERBOSE);
  771.  
  772.         return $ret;
  773.     }
  774.  
  775.     // one step in a recursive DFS traversal of the target dependency tree.
  776.     // - The array "state" contains the state (VISITED or VISITING or null)
  777.     //   of all the target names.
  778.     // - The stack "visiting" contains a stack of target names that are
  779.     //   currently on the DFS stack. (NB: the target names in "visiting" are
  780.     //    exactly the target names in "state" that are in the VISITING state.)
  781.     // 1. Set the current target to the VISITING state, and push it onto
  782.     //    the "visiting" stack.
  783.     // 2. Throw a BuildException if any child of the current node is
  784.     //    in the VISITING state (implies there is a cycle.) It uses the
  785.     //    "visiting" Stack to construct the cycle.
  786.     // 3. If any children have not been VISITED, tsort() the child.
  787.     // 4. Add the current target to the Vector "ret" after the children
  788.     //    have been visited. Move the current target to the VISITED state.
  789.     //    "ret" now contains the sorted sequence of Targets upto the current
  790.     //    Target.
  791.  
  792.     function _tsort($root, &$targets, &$state, &$visiting, &$ret) {
  793.         $state[$root] = "VISITING";
  794.         $visiting[]  = $root;
  795.  
  796.         if (!isset($targets[$root]) || !($targets[$root] instanceof Target)) {
  797.             $target = null;
  798.         } else {
  799.             $target = $targets[$root];
  800.         }
  801.  
  802.         // make sure we exist
  803.         if ($target === null) {
  804.             $sb = "Target '$root' does not exist in this project.";
  805.             array_pop($visiting);
  806.             if (!empty($visiting)) {
  807.                 $parent = (string) $visiting[count($visiting)-1];
  808.                 $sb .= "It is used from target '$parent'.";
  809.             }
  810.             throw new BuildException($sb);
  811.         }
  812.  
  813.         $deps = $target->getDependencies();
  814.  
  815.         while($deps) {
  816.             $cur = (string) array_shift($deps);
  817.             if (!isset($state[$cur])) {
  818.                 $m = null;
  819.             } else {
  820.                 $m = (string) $state[$cur];
  821.             }
  822.             if ($m === null) {
  823.                 // not been visited
  824.                 $this->_tsort($cur, $targets, $state, $visiting, $ret);
  825.             } elseif ($m == "VISITING") {
  826.                 // currently visiting this node, so have a cycle
  827.                 throw $this->_makeCircularException($cur, $visiting);
  828.             }
  829.         }
  830.  
  831.         $p = (string) array_pop($visiting);
  832.         if ($root !== $p) {
  833.             throw new Exception("Unexpected internal error: expected to pop $root but got $p");
  834.         }
  835.  
  836.         $state[$root] = "VISITED";
  837.         $ret[] = $target;
  838.     }
  839.  
  840.     function _makeCircularException($end, $stk) {
  841.         $sb = "Circular dependency: $end";
  842.         do {
  843.             $sb .= " <- ".(string) array_pop($stk);
  844.         } while($c != $end);
  845.         return new BuildException($sb);
  846.     }
  847.  
  848.     /**
  849.      * Adds a reference to an object. This method is called when the parser
  850.      * detects a id="foo" attribute. It passes the id as $name and a reference
  851.      * to the object assigned to this id as $value
  852.      */
  853.     function addReference($name, $object) {
  854.         if (isset($this->references[$name])) {
  855.             $this->log("Overriding previous definition of reference to $name", PROJECT_MSG_WARN);
  856.         }
  857.         $this->log("Adding reference: $name -> ".get_class($object), PROJECT_MSG_DEBUG);
  858.         $this->references[$name] = $object;
  859.     }
  860.  
  861.     /**
  862.      * Returns the references array.
  863.      * @return array
  864.      */
  865.     function getReferences() {
  866.         return $this->references;
  867.     }
  868.     
  869.     /**
  870.      * Returns a specific reference.
  871.      * @param string $key The reference id/key.
  872.      * @return object or null if not defined
  873.      */
  874.     function getReference($key)
  875.     {
  876.         if (isset($this->references[$key])) {
  877.             return $this->references[$key];
  878.         }
  879.         return null; // just to be explicit
  880.     }
  881.  
  882.     /**
  883.      * Abstracting and simplifyling Logger calls for project messages
  884.      */
  885.     function log($msg, $level = PROJECT_MSG_INFO) {
  886.         $this->logObject($this, $msg, $level);
  887.     }
  888.  
  889.     function logObject($obj, $msg, $level) {
  890.         $this->fireMessageLogged($obj, $msg, $level);
  891.     }
  892.  
  893.     function addBuildListener(BuildListener $listener) {
  894.         $this->listeners[] = $listener;
  895.     }
  896.  
  897.     function removeBuildListener(BuildListener $listener) {
  898.         $newarray = array();
  899.         for ($i=0, $size=count($this->listeners); $i < $size; $i++) {
  900.             if ($this->listeners[$i] !== $listener) {
  901.                 $newarray[] = $this->listeners[$i];
  902.             }
  903.         }
  904.         $this->listeners = $newarray;
  905.     }
  906.  
  907.     function getBuildListeners() {
  908.         return $this->listeners;
  909.     }
  910.  
  911.     function fireBuildStarted() {
  912.         $event = new BuildEvent($this);        
  913.         foreach($this->listeners as $listener) {
  914.             $listener->buildStarted($event);
  915.         }
  916.     }
  917.  
  918.     function fireBuildFinished($exception) {        
  919.         $event = new BuildEvent($this);
  920.         $event->setException($exception);
  921.         foreach($this->listeners as $listener) {
  922.             $listener->buildFinished($event);
  923.         }
  924.     }
  925.  
  926.     function fireTargetStarted($target) {
  927.         $event = new BuildEvent($target);        
  928.            foreach($this->listeners as $listener) {
  929.             $listener->targetStarted($event);
  930.         }
  931.     }
  932.  
  933.     function fireTargetFinished($target, $exception) {
  934.         $event = new BuildEvent($target);        
  935.         $event->setException($exception);
  936.         foreach($this->listeners as $listener) {
  937.             $listener->targetFinished($event);
  938.         }
  939.     }
  940.  
  941.     function fireTaskStarted($task) {
  942.         $event = new BuildEvent($task);        
  943.         foreach($this->listeners as $listener) {
  944.             $listener->taskStarted($event);
  945.         }
  946.     }
  947.  
  948.     function fireTaskFinished($task, $exception) {
  949.         $event = new BuildEvent($task);        
  950.         $event->setException($exception);
  951.         foreach($this->listeners as $listener) {
  952.             $listener->taskFinished($event);
  953.         }
  954.     }
  955.  
  956.     function fireMessageLoggedEvent($event, $message, $priority) {
  957.         $event->setMessage($message, $priority);
  958.         foreach($this->listeners as $listener) {
  959.             $listener->messageLogged($event);
  960.         }
  961.     }
  962.  
  963.     function fireMessageLogged($object, $message, $priority) {
  964.         $this->fireMessageLoggedEvent(new BuildEvent($object), $message, $priority);
  965.     }
  966. }
  967.